/**
 *	---------------------------------------------------------------------------
 *	COMP 6231/FALL 2013
 *	Assignment - 02
 *	@author Talha Rahman (1296884)
 *	---------------------------------------------------------------------------
 *	This is the main CORBA server class. This class provides all the CORBA services
 *	and also creates threads to provide UDP services or send UDP requests to 
 *  other servers.
 *	  
 */
package ca.concordia.comp6231.frontend;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.omg.CORBA.ORBPackage.InvalidName;
import org.omg.PortableServer.POAManagerPackage.AdapterInactive;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;

import ca.concordia.comp6231.corba.CORBAService;
import ca.concordia.comp6231.udp.UDPClientThread;
import ca.concordia.comp6231.udp.UDPService;
import corba.FrontEndCORBAInterfacePOA;

public class FrontEnd extends FrontEndCORBAInterfacePOA{
	private String serverName;
	private FrontEndConfiguration config;
	private Thread udpService;
	private Thread corbaService;
	private UDPClientThread udpClientThread;
	
	private final static Logger logger = Logger.getLogger(FrontEnd.class.getName());
	
	private static long messageID = 1l;
	
	/**
	 * 
	 * @param serverName The name of the server (i.e. SPVM)
	 * @param args Any command line arguments passed during initialization
	 * @throws InvalidName
	 * @throws ServantAlreadyActive
	 * @throws WrongPolicy
	 * @throws ObjectNotActive
	 * @throws FileNotFoundException
	 * @throws AdapterInactive
	 */
	public FrontEnd(String serverName, String[] args) throws InvalidName, ServantAlreadyActive, WrongPolicy, ObjectNotActive, FileNotFoundException, AdapterInactive{
		try {
			config = new FrontEndConfiguration(serverName);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Couldn't load configuration. Invalid server name: " + serverName);
			System.exit(-1);
		}
		
		String logFileName = "FE_" + System.currentTimeMillis() + ".log";
		initLogging(logFileName);
		
		loadCORBAService(args);
		loadUDPService();
	}
	
	
	/**
	 * Loads the UDP service.
	 */
	private void loadUDPService(){
		int udpPortNo = config.getUDPPortNo();
		udpService = new Thread(new UDPService(udpPortNo, this));
		
		try{
			udpService.start();
			String message = " FE started running UDP service at port: " +	udpPortNo + ".";
			logger.log(Level.INFO, message);
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + message);
		}catch(Exception e){
			String message = " FE failed to start UDP service at port: " + udpPortNo + ".";
			logger.log(Level.SEVERE, message);
			System.out.println("[" + new Date(System.currentTimeMillis()) + "]" + message);
		}		
	}
	
	private void loadCORBAService(String[] args) throws InvalidName, ServantAlreadyActive, WrongPolicy, ObjectNotActive, FileNotFoundException, AdapterInactive{
		
		corbaService = new Thread(new CORBAService(this, args));
		try{
			corbaService.start();
		}catch(Exception e){
			
		}
	}
	
	/**
	 * Sends a UDP message by creating an UDP client thread. 
	 * @see {@link UDPClientThread}
	 * @param message The UDP message to send.
	 * @param hostName The UDP host to send the message to.
	 * @param portNo The UDP port no.
	 * @return An instance of the UDP Client Thread.
	 */
	public UDPClientThread sendUDPMessage(String message, String hostName, int portNo){
		udpClientThread = new UDPClientThread(message, hostName, portNo);
		udpClientThread.start();
		
		return udpClientThread;
	}
	
	/**
	 * Creates a Criminal Record.
	 * @return The server response for this attempted action.
	 */
	@Override
	public String createCRecord(String fName, String lName, String crimeDesc, String status, String badgeID) {
		String functionName = "createCRecord";
		String requestString = "functionName:" + functionName + ",messageID:" + messageID++ + ",fName:" + fName + ",lName:" + lName + ",crimeDesc:" + crimeDesc
				+",status:" + status + ",badgeID:" + badgeID;
		String stationName = extractStationNameFromBadgeID(badgeID);
		return sendUDPRequests(requestString, stationName);
	}
	
	/**
	 * Creates a Missing Record.
	 * @return The server response for this attempted action.
	 */
	@Override
	public String createMRecord(String fName, String lName, String description, String address, String lastDate,
							String lastLocation, String status, String badgeID) {
		
		String functionName = "createMRecord";
		String requestString = "functionName:" + functionName + ",messageID:" + messageID++ + ",fName:" + fName + ",lName:" + lName + ",crimeDesc:" + description
				+",status:" + status + ",badgeID:" + badgeID + ",address:" + address + ",lastDate:" + lastDate + ",lastLocation:" + lastLocation;
		String stationName = extractStationNameFromBadgeID(badgeID);
		return sendUDPRequests(requestString, stationName);
	}
	
	/**
	 * Gets the the record count for all three servers.
	 * @return The server response for this attempted action.
	 */
	@Override
	public String getRecordCounts(String badgeID){
		
		String functionName = "getRecordCount";
		String requestString = "functionName:" + functionName + ",messageID:" + messageID++ + "";
		String stationName = extractStationNameFromBadgeID(badgeID);
		return sendUDPRequests(requestString, stationName);

	}
	
	/**
	 * Edits a record
	 * @return The server response for this attempted action.
	 */
	@Override
	public String editRecord(String lName, String recordID, String newStatus, String badgeID) {		
		String functionName = "editRecord";
		String requestString = "functionName:" + functionName + ",messageID:" + messageID++ + ",lName:" + lName + ",recordID:" + recordID
				+",newStatus:" + newStatus + ",badgeID:" + badgeID ;
		String stationName = extractStationNameFromBadgeID(badgeID);
		return sendUDPRequests(requestString, stationName);
	}
	
	@Override
	public String transferRecord(String badgeID, String recordID, String remoteStationServerName) {
		UDPClientThread udpRequest = sendUDPMessage("transferRecord",
				"localhost", 7780);

		try {
			udpRequest.join();
		} catch (Exception e) {

		}

		return udpRequest.getUDPResponseString();
	}
	
	@Override
	public void printAllRecords(){
		System.out.println("printAllRecords");
	}

	/**
	 * Initialize the logging objects for the server application
	 * @param fileName
	 */
	private static void initLogging(String fileName){
		FileHandler fh = null;
		try {
			fh = new FileHandler(fileName, false);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		fh.setFormatter(new SimpleFormatter()); 
		logger.setUseParentHandlers(false);
		logger.addHandler(fh);
		
		logger.setLevel(Level.INFO);
	}
	
	private String sendUDPRequests(String udpRequestString, String stationName) {
		
		List<UDPClientThread> udpRequestList = new ArrayList<UDPClientThread>();
		
		boolean replica1Exception = false, replica2Exception = false, replica3Exception = false;
		
		UDPClientThread udpRequestROS = sendUDPMessage(udpRequestString, "localhost", 7780);
		String stationName1 = "SPVM", stationName2 = "SPL", stationName3 = "SPB";
		
		UDPClientThread udpRequestReplica1 = null, udpRequestReplica2 = null, udpRequestReplica3 = null;
		
		try {
			udpRequestReplica1 = sendUDPMessage(udpRequestString, config.getHostName("R1_"+stationName1), config.getUDPPortNo("R1_"+stationName1));
		} catch (Exception e) {
			e.printStackTrace();
			replica1Exception = true;
		}
		
		
		try {
			udpRequestReplica2 = sendUDPMessage(udpRequestString, config.getHostName("R1_"+stationName2), config.getUDPPortNo("R1_"+stationName2));
		} catch (Exception e) {
			e.printStackTrace();
			replica2Exception = true;
		}
		
		try {
			udpRequestReplica3 = sendUDPMessage(udpRequestString, config.getHostName("R1_"+stationName3), config.getUDPPortNo("R1_"+stationName3));
		} catch (Exception e) {
			e.printStackTrace();
			replica3Exception = true;
		}
		
		udpRequestList.add(udpRequestROS);
		udpRequestList.add(udpRequestReplica1);
		udpRequestList.add(udpRequestReplica2);
		udpRequestList.add(udpRequestReplica3);

		for (UDPClientThread udpRequest : udpRequestList) {
			try {
				udpRequest.join(20000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		String udpResponseReplica1 = "", udpResponseReplica2 = "", udpResponseReplica3 = ""; 
		boolean replica1Timeout = false, replica2Timeout = false, replica3Timeout = false;
		int timeOutCount = 0;
		if(!udpRequestReplica1.isAlive()) {
			udpResponseReplica1 = udpRequestReplica1.getUDPResponseString().trim();
		} else {
			udpResponseReplica1 = "message:timeout";
			replica1Timeout = true;
			timeOutCount++;
		}
		
		if(!udpRequestReplica2.isAlive()) {
			udpResponseReplica2 = udpRequestReplica2.getUDPResponseString().trim();
		} else {
			udpResponseReplica2 = "message:timeout";
			replica2Timeout = true;
			timeOutCount++;
		}
		
		if(!udpRequestReplica3.isAlive()) {
			udpResponseReplica3 = udpRequestReplica3.getUDPResponseString().trim();
		} else {
			udpResponseReplica3 = "message:timeout";
			replica3Timeout = true;
			timeOutCount++;
		}
		
		String successMessage = "message:success";
		int exceptionCount = 0, replicaDataMismatch = 0;
		if(!udpResponseReplica1.startsWith(successMessage)) {
			replica1Exception = true;
			exceptionCount++;
		}
		if(!udpResponseReplica2.startsWith(successMessage)) {
			replica2Exception = true;
			exceptionCount++;
		}
		if(!udpResponseReplica1.startsWith(successMessage)) {
			replica3Exception = true;
			exceptionCount++;
		}
		
		System.out.println("Exception count:" + exceptionCount);
		System.out.println(udpResponseReplica1);
		System.out.println(udpResponseReplica2);
		System.out.println(udpResponseReplica3);
		
		if(udpResponseReplica1.equals(udpResponseReplica2) && !udpResponseReplica1.equals(udpResponseReplica3)) {
			replica3Exception = true;
			replicaDataMismatch++;
		}
		
		if(udpResponseReplica1.equals(udpResponseReplica3) && !udpResponseReplica1.equals(udpResponseReplica2)) {
			replica2Exception = true;
			replicaDataMismatch++;
		}
		
		if(udpResponseReplica2.equals(udpResponseReplica3) && !udpResponseReplica2.equals(udpResponseReplica1)) {
			replica1Exception = true;
			replicaDataMismatch++;
		}
		
		StringBuilder response = new StringBuilder();
		if(exceptionCount == 0 && replicaDataMismatch == 0) {
			response.append("response:{" + udpResponseReplica1 + "}");
		}else if(exceptionCount > 1 || replicaDataMismatch > 1) {
			response.append("{System unstable}");
		} else if(replica1Exception) {
			response.append("response:{" + udpResponseReplica2 + "}");
		} else if(replica2Exception) {
			response.append("response:{" + udpResponseReplica3 + "}");
		} else if(replica3Exception) {
			response.append("response:{" + udpResponseReplica1 + "}");
		}
		
		if(exceptionCount > 0 || replicaDataMismatch > 0 || timeOutCount > 0) {
			
			String replicaName = "";
			
			if(replica1Exception || replica1Timeout) {
				replicaName = "R1";
			}
			
			if(replica2Exception || replica2Timeout) {
				replicaName = "R2";
			}
			
			if(replica3Exception || replica3Timeout) {
				replicaName = "R3";
			}
			
			String failureType = "";
			
			if(replica1Exception || replica2Exception | replica3Exception) {
				failureType = "error";
			} else if(replica1Timeout || replica2Timeout || replica3Timeout) {
				failureType = "timeout";
			}

			
			String requestString = "failureType:" + failureType + ",replicaName:" + replicaName+ "";
			UDPClientThread udpRequestRM = sendUDPMessage(requestString, config.getHostName("RM"), config.getUDPPortNo("RM"));
		}
		
		
		return new String(response);
	
	}
	
	private static String extractStationNameFromBadgeID(String badgeID){
		try{
			int i = 0;
			while (!Character.isDigit(badgeID.charAt(i))){
				i++;
			}
			
			return badgeID.substring(0,i);
		}
		catch(Exception e){
			System.out.println("Invalid badge ID:");
			return null;
		}
	}
}
